<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=Edge">
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" type="text/css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" type="text/css">
    <link rel="stylesheet" href="./resources/prism/prism.css" type="text/css">
    <link rel="stylesheet" href="../css/ol.css" type="text/css">
    <link rel="stylesheet" href="./resources/layout.css" type="text/css">
    
    <link rel="stylesheet" href="color-manipulation.css">
    <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=fetch,requestAnimationFrame,Element.prototype.classList,URL"></script>
    <script src="./resources/zeroclipboard/ZeroClipboard.min.js"></script>
    <title>Color Manipulation</title>
  </head>
  <body>

    <header class="navbar" role="navigation">
      <div class="container">
        <div class="display-table pull-left" id="navbar-logo-container">
          <a class="navbar-brand" href="./"><img src="./resources/logo-70x70.png">&nbsp;OpenLayers Examples</a>
        </div>
        <!-- menu items that get hidden below 768px width -->
        <nav class='collapse navbar-collapse navbar-responsive-collapse'>
          <ul class="nav navbar-nav pull-right">
            <li><a href="../doc">Docs</a></li>
            <li><a class="active" href="index.html">Examples</a></li>
            <li><a href="../apidoc">API</a></li>
            <li><a href="https://github.com/openlayers/openlayers">Code</a></li>
          </ul>
        </nav>
      </div>
    </header>

    <div class="container-fluid">

      <div id="latest-check" class="alert alert-warning alert-dismissible" role="alert" style="display:none">
        <button id="latest-dismiss" type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        This example uses OpenLayers v<span>4.6.5</span>. The <a id="latest-link" href="#" class="alert-link">latest</a> is v<span id="latest-version"></span>.
      </div>

      <div class="row-fluid">
        <div class="span12">
          <h4 id="title">Color Manipulation</h4>
          <div id="map" class="map"></div>
<table class="controls">
  <tr>
    <td>hue</td>
    <td><span id="hueOut"></span>°</td>
    <td><input id="hue" type="range" min="-180" max="180" value="0"/></td>
  </tr>
  <tr>
    <td>chroma</td>
    <td><span id="chromaOut"></span> %</td>
    <td><input id="chroma" type="range" min="0" max="100" value="100"/></td>
  </tr>
  <tr>
    <td>lightness</td>
    <td><span id="lightnessOut"></span> %</td>
    <td><input id="lightness" type="range" min="0" max="100" value="100"/></td>
  </tr>
</table>

        </div>
      </div>

      <div class="row-fluid">
        <div class="span12">
          <p id="shortdesc">Demonstrates color manipulation with a raster source.</p>
          <div id="docs"><p>A raster source allows arbitrary manipulation of pixel values.  In this example, RGB values on the input tile source are adjusted in a pixel-wise operation before being rendered with a second raster source.  The raster operation takes pixels in in RGB space, converts them to HCL color space, adjusts the values based on the controls above, and then converts them back to RGB space for rendering.</p>
</div>
          <div id="api-links">Related API documentation: <ul class="inline"><li><a href="../apidoc/ol.Map.html" title="API documentation for ol.Map">ol.Map</a></li>,<li><a href="../apidoc/ol.View.html" title="API documentation for ol.View">ol.View</a></li>,<li><a href="../apidoc/ol.layer.Image.html" title="API documentation for ol.layer.Image">ol.layer.Image</a></li>,<li><a href="../apidoc/ol.source.Raster.html" title="API documentation for ol.source.Raster">ol.source.Raster</a></li>,<li><a href="../apidoc/ol.source.Stamen.html" title="API documentation for ol.source.Stamen">ol.source.Stamen</a></li></ul></div>
        </div>
      </div>

      <div class="row-fluid">
        <div id="source-controls">
          <a id="copy-button"><i class="fa fa-clipboard"></i> Copy</a>
          <a id="codepen-button"><i class="fa fa-codepen"></i> Edit</a>
        </div>
        <form method="POST" id="codepen-form" target="_blank" action="https://codepen.io/pen/define/">
          <textarea class="hidden" name="title">Color Manipulation</textarea>
          <textarea class="hidden" name="description">Demonstrates color manipulation with a raster source.</textarea>
          <textarea class="hidden" name="js">/**
 * Color manipulation functions below are adapted from
 * https://github.com/d3/d3-color.
 */
var Xn &#x3D; 0.950470;
var Yn &#x3D; 1;
var Zn &#x3D; 1.088830;
var t0 &#x3D; 4 / 29;
var t1 &#x3D; 6 / 29;
var t2 &#x3D; 3 * t1 * t1;
var t3 &#x3D; t1 * t1 * t1;
var twoPi &#x3D; 2 * Math.PI;


/**
 * Convert an RGB pixel into an HCL pixel.
 * @param {Array.&lt;number&gt;} pixel A pixel in RGB space.
 * @return {Array.&lt;number&gt;} A pixel in HCL space.
 */
function rgb2hcl(pixel) {
  var red &#x3D; rgb2xyz(pixel[0]);
  var green &#x3D; rgb2xyz(pixel[1]);
  var blue &#x3D; rgb2xyz(pixel[2]);

  var x &#x3D; xyz2lab(
      (0.4124564 * red + 0.3575761 * green + 0.1804375 * blue) / Xn);
  var y &#x3D; xyz2lab(
      (0.2126729 * red + 0.7151522 * green + 0.0721750 * blue) / Yn);
  var z &#x3D; xyz2lab(
      (0.0193339 * red + 0.1191920 * green + 0.9503041 * blue) / Zn);

  var l &#x3D; 116 * y - 16;
  var a &#x3D; 500 * (x - y);
  var b &#x3D; 200 * (y - z);

  var c &#x3D; Math.sqrt(a * a + b * b);
  var h &#x3D; Math.atan2(b, a);
  if (h &lt; 0) {
    h +&#x3D; twoPi;
  }

  pixel[0] &#x3D; h;
  pixel[1] &#x3D; c;
  pixel[2] &#x3D; l;

  return pixel;
}


/**
 * Convert an HCL pixel into an RGB pixel.
 * @param {Array.&lt;number&gt;} pixel A pixel in HCL space.
 * @return {Array.&lt;number&gt;} A pixel in RGB space.
 */
function hcl2rgb(pixel) {
  var h &#x3D; pixel[0];
  var c &#x3D; pixel[1];
  var l &#x3D; pixel[2];

  var a &#x3D; Math.cos(h) * c;
  var b &#x3D; Math.sin(h) * c;

  var y &#x3D; (l + 16) / 116;
  var x &#x3D; isNaN(a) ? y : y + a / 500;
  var z &#x3D; isNaN(b) ? y : y - b / 200;

  y &#x3D; Yn * lab2xyz(y);
  x &#x3D; Xn * lab2xyz(x);
  z &#x3D; Zn * lab2xyz(z);

  pixel[0] &#x3D; xyz2rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z);
  pixel[1] &#x3D; xyz2rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z);
  pixel[2] &#x3D; xyz2rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z);

  return pixel;
}

function xyz2lab(t) {
  return t &gt; t3 ? Math.pow(t, 1 / 3) : t / t2 + t0;
}

function lab2xyz(t) {
  return t &gt; t1 ? t * t * t : t2 * (t - t0);
}

function rgb2xyz(x) {
  return (x /&#x3D; 255) &lt;&#x3D; 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
}

function xyz2rgb(x) {
  return 255 * (x &lt;&#x3D; 0.0031308 ?
    12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055);
}

var raster &#x3D; new ol.source.Raster({
  sources: [new ol.source.Stamen({
    layer: &#x27;watercolor&#x27;,
    transition: 0
  })],
  operation: function(pixels, data) {
    var hcl &#x3D; rgb2hcl(pixels[0]);

    var h &#x3D; hcl[0] + Math.PI * data.hue / 180;
    if (h &lt; 0) {
      h +&#x3D; twoPi;
    } else if (h &gt; twoPi) {
      h -&#x3D; twoPi;
    }
    hcl[0] &#x3D; h;

    hcl[1] *&#x3D; (data.chroma / 100);
    hcl[2] *&#x3D; (data.lightness / 100);

    return hcl2rgb(hcl);
  },
  lib: {
    rgb2hcl: rgb2hcl,
    hcl2rgb: hcl2rgb,
    rgb2xyz: rgb2xyz,
    lab2xyz: lab2xyz,
    xyz2lab: xyz2lab,
    xyz2rgb: xyz2rgb,
    Xn: Xn,
    Yn: Yn,
    Zn: Zn,
    t0: t0,
    t1: t1,
    t2: t2,
    t3: t3,
    twoPi: twoPi
  }
});

var controls &#x3D; {};

raster.on(&#x27;beforeoperations&#x27;, function(event) {
  var data &#x3D; event.data;
  for (var id in controls) {
    data[id] &#x3D; Number(controls[id].value);
  }
});

var map &#x3D; new ol.Map({
  layers: [
    new ol.layer.Image({
      source: raster
    })
  ],
  target: &#x27;map&#x27;,
  view: new ol.View({
    center: [0, 2500000],
    zoom: 2,
    maxZoom: 18
  })
});

var controlIds &#x3D; [&#x27;hue&#x27;, &#x27;chroma&#x27;, &#x27;lightness&#x27;];
controlIds.forEach(function(id) {
  var control &#x3D; document.getElementById(id);
  var output &#x3D; document.getElementById(id + &#x27;Out&#x27;);
  control.addEventListener(&#x27;input&#x27;, function() {
    output.innerText &#x3D; control.value;
    raster.changed();
  });
  output.innerText &#x3D; control.value;
  controls[id] &#x3D; control;
});
</textarea>
          <textarea class="hidden" name="css">table.controls td {
  text-align: center;
  padding: 2px 5px;
  min-width: 60px;
}
</textarea>
          <textarea class="hidden" name="html">&lt;div id&#x3D;&quot;map&quot; class&#x3D;&quot;map&quot;&gt;&lt;/div&gt;
&lt;table class&#x3D;&quot;controls&quot;&gt;
  &lt;tr&gt;
    &lt;td&gt;hue&lt;/td&gt;
    &lt;td&gt;&lt;span id&#x3D;&quot;hueOut&quot;&gt;&lt;/span&gt;°&lt;/td&gt;
    &lt;td&gt;&lt;input id&#x3D;&quot;hue&quot; type&#x3D;&quot;range&quot; min&#x3D;&quot;-180&quot; max&#x3D;&quot;180&quot; value&#x3D;&quot;0&quot;/&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;chroma&lt;/td&gt;
    &lt;td&gt;&lt;span id&#x3D;&quot;chromaOut&quot;&gt;&lt;/span&gt; %&lt;/td&gt;
    &lt;td&gt;&lt;input id&#x3D;&quot;chroma&quot; type&#x3D;&quot;range&quot; min&#x3D;&quot;0&quot; max&#x3D;&quot;100&quot; value&#x3D;&quot;100&quot;/&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;lightness&lt;/td&gt;
    &lt;td&gt;&lt;span id&#x3D;&quot;lightnessOut&quot;&gt;&lt;/span&gt; %&lt;/td&gt;
    &lt;td&gt;&lt;input id&#x3D;&quot;lightness&quot; type&#x3D;&quot;range&quot; min&#x3D;&quot;0&quot; max&#x3D;&quot;100&quot; value&#x3D;&quot;100&quot;/&gt;&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
</textarea>
          <input type="hidden" name="resources" value="https://openlayers.org/en/v4.6.5/css/ol.css,https://openlayers.org/en/v4.6.5/build/ol.js">
          <input type="hidden" name="data">
        </form>
        <pre><code id="example-source" class="language-markup">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Color Manipulation&lt;/title&gt;
    &lt;link rel="stylesheet" href="https://openlayers.org/en/v4.6.5/css/ol.css" type="text/css"&gt;
    &lt;!-- The line below is only needed for old environments like Internet Explorer and Android 4.x --&gt;
    &lt;script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"&gt;&lt;/script&gt;
    &lt;script src="https://openlayers.org/en/v4.6.5/build/ol.js"&gt;&lt;/script&gt;
    &lt;style&gt;
      table.controls td {
        text-align: center;
        padding: 2px 5px;
        min-width: 60px;
      }
    &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id&#x3D;&quot;map&quot; class&#x3D;&quot;map&quot;&gt;&lt;/div&gt;
    &lt;table class&#x3D;&quot;controls&quot;&gt;
      &lt;tr&gt;
        &lt;td&gt;hue&lt;/td&gt;
        &lt;td&gt;&lt;span id&#x3D;&quot;hueOut&quot;&gt;&lt;/span&gt;°&lt;/td&gt;
        &lt;td&gt;&lt;input id&#x3D;&quot;hue&quot; type&#x3D;&quot;range&quot; min&#x3D;&quot;-180&quot; max&#x3D;&quot;180&quot; value&#x3D;&quot;0&quot;/&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;chroma&lt;/td&gt;
        &lt;td&gt;&lt;span id&#x3D;&quot;chromaOut&quot;&gt;&lt;/span&gt; %&lt;/td&gt;
        &lt;td&gt;&lt;input id&#x3D;&quot;chroma&quot; type&#x3D;&quot;range&quot; min&#x3D;&quot;0&quot; max&#x3D;&quot;100&quot; value&#x3D;&quot;100&quot;/&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;lightness&lt;/td&gt;
        &lt;td&gt;&lt;span id&#x3D;&quot;lightnessOut&quot;&gt;&lt;/span&gt; %&lt;/td&gt;
        &lt;td&gt;&lt;input id&#x3D;&quot;lightness&quot; type&#x3D;&quot;range&quot; min&#x3D;&quot;0&quot; max&#x3D;&quot;100&quot; value&#x3D;&quot;100&quot;/&gt;&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/table&gt;
    &lt;script&gt;
      /**
       * Color manipulation functions below are adapted from
       * https://github.com/d3/d3-color.
       */
      var Xn &#x3D; 0.950470;
      var Yn &#x3D; 1;
      var Zn &#x3D; 1.088830;
      var t0 &#x3D; 4 / 29;
      var t1 &#x3D; 6 / 29;
      var t2 &#x3D; 3 * t1 * t1;
      var t3 &#x3D; t1 * t1 * t1;
      var twoPi &#x3D; 2 * Math.PI;


      /**
       * Convert an RGB pixel into an HCL pixel.
       * @param {Array.&lt;number&gt;} pixel A pixel in RGB space.
       * @return {Array.&lt;number&gt;} A pixel in HCL space.
       */
      function rgb2hcl(pixel) {
        var red &#x3D; rgb2xyz(pixel[0]);
        var green &#x3D; rgb2xyz(pixel[1]);
        var blue &#x3D; rgb2xyz(pixel[2]);

        var x &#x3D; xyz2lab(
            (0.4124564 * red + 0.3575761 * green + 0.1804375 * blue) / Xn);
        var y &#x3D; xyz2lab(
            (0.2126729 * red + 0.7151522 * green + 0.0721750 * blue) / Yn);
        var z &#x3D; xyz2lab(
            (0.0193339 * red + 0.1191920 * green + 0.9503041 * blue) / Zn);

        var l &#x3D; 116 * y - 16;
        var a &#x3D; 500 * (x - y);
        var b &#x3D; 200 * (y - z);

        var c &#x3D; Math.sqrt(a * a + b * b);
        var h &#x3D; Math.atan2(b, a);
        if (h &lt; 0) {
          h +&#x3D; twoPi;
        }

        pixel[0] &#x3D; h;
        pixel[1] &#x3D; c;
        pixel[2] &#x3D; l;

        return pixel;
      }


      /**
       * Convert an HCL pixel into an RGB pixel.
       * @param {Array.&lt;number&gt;} pixel A pixel in HCL space.
       * @return {Array.&lt;number&gt;} A pixel in RGB space.
       */
      function hcl2rgb(pixel) {
        var h &#x3D; pixel[0];
        var c &#x3D; pixel[1];
        var l &#x3D; pixel[2];

        var a &#x3D; Math.cos(h) * c;
        var b &#x3D; Math.sin(h) * c;

        var y &#x3D; (l + 16) / 116;
        var x &#x3D; isNaN(a) ? y : y + a / 500;
        var z &#x3D; isNaN(b) ? y : y - b / 200;

        y &#x3D; Yn * lab2xyz(y);
        x &#x3D; Xn * lab2xyz(x);
        z &#x3D; Zn * lab2xyz(z);

        pixel[0] &#x3D; xyz2rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z);
        pixel[1] &#x3D; xyz2rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z);
        pixel[2] &#x3D; xyz2rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z);

        return pixel;
      }

      function xyz2lab(t) {
        return t &gt; t3 ? Math.pow(t, 1 / 3) : t / t2 + t0;
      }

      function lab2xyz(t) {
        return t &gt; t1 ? t * t * t : t2 * (t - t0);
      }

      function rgb2xyz(x) {
        return (x /&#x3D; 255) &lt;&#x3D; 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
      }

      function xyz2rgb(x) {
        return 255 * (x &lt;&#x3D; 0.0031308 ?
          12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055);
      }

      var raster &#x3D; new ol.source.Raster({
        sources: [new ol.source.Stamen({
          layer: &#x27;watercolor&#x27;,
          transition: 0
        })],
        operation: function(pixels, data) {
          var hcl &#x3D; rgb2hcl(pixels[0]);

          var h &#x3D; hcl[0] + Math.PI * data.hue / 180;
          if (h &lt; 0) {
            h +&#x3D; twoPi;
          } else if (h &gt; twoPi) {
            h -&#x3D; twoPi;
          }
          hcl[0] &#x3D; h;

          hcl[1] *&#x3D; (data.chroma / 100);
          hcl[2] *&#x3D; (data.lightness / 100);

          return hcl2rgb(hcl);
        },
        lib: {
          rgb2hcl: rgb2hcl,
          hcl2rgb: hcl2rgb,
          rgb2xyz: rgb2xyz,
          lab2xyz: lab2xyz,
          xyz2lab: xyz2lab,
          xyz2rgb: xyz2rgb,
          Xn: Xn,
          Yn: Yn,
          Zn: Zn,
          t0: t0,
          t1: t1,
          t2: t2,
          t3: t3,
          twoPi: twoPi
        }
      });

      var controls &#x3D; {};

      raster.on(&#x27;beforeoperations&#x27;, function(event) {
        var data &#x3D; event.data;
        for (var id in controls) {
          data[id] &#x3D; Number(controls[id].value);
        }
      });

      var map &#x3D; new ol.Map({
        layers: [
          new ol.layer.Image({
            source: raster
          })
        ],
        target: &#x27;map&#x27;,
        view: new ol.View({
          center: [0, 2500000],
          zoom: 2,
          maxZoom: 18
        })
      });

      var controlIds &#x3D; [&#x27;hue&#x27;, &#x27;chroma&#x27;, &#x27;lightness&#x27;];
      controlIds.forEach(function(id) {
        var control &#x3D; document.getElementById(id);
        var output &#x3D; document.getElementById(id + &#x27;Out&#x27;);
        control.addEventListener(&#x27;input&#x27;, function() {
          output.innerText &#x3D; control.value;
          raster.changed();
        });
        output.innerText &#x3D; control.value;
        controls[id] &#x3D; control;
      });
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
      </div>
    </div>

    <script src="./resources/common.js"></script>
    <script src="./resources/prism/prism.min.js"></script>
    <script src="loader.js?id=color-manipulation"></script>
  </body>
  <script>
  var packageUrl = 'https://raw.githubusercontent.com/openlayers/openlayers.github.io/build/package.json';
  fetch(packageUrl).then(function(response) {
    return response.json();
  }).then(function(json) {
    var latestVersion = json.version;
    document.getElementById('latest-version').innerHTML = latestVersion;
    var url = window.location.href;
    var branchSearch = url.match(/\/([^\/]*)\/examples\//);
    var cookieText = 'dismissed=-' + latestVersion + '-';
    var dismissed = document.cookie.indexOf(cookieText) != -1;
    if (!dismissed && /^v[0-9\.]*$/.test(branchSearch[1]) && '4.6.5' != latestVersion) {
      var link = url.replace(branchSearch[0], '/latest/examples/');
      fetch(link, {method: 'head'}).then(function(response) {
        var a = document.getElementById('latest-link');
        a.href = response.status == 200 ? link : '../../latest/examples/';
      });
      var latestCheck = document.getElementById('latest-check');
      latestCheck.style.display = '';
      document.getElementById('latest-dismiss').onclick = function() {
        latestCheck.style.display = 'none';
        document.cookie = cookieText;
      }
    }
  });
  </script>
</html>
